package xhq.java.test.utilities.perform;

import xhq.java.test.utilities.types.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @author hq_xu
 * @since 2021/2/4 9:34
 */
public class PerformCounter {
    // region static
    private static final short DEFAULT_INITIAL_MESSAGE_LENGHT = 128; // stringbuilder 默认初始大小
    private static final ThreadLocal<PerformCounter> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * get or create(if not exists) a PerformCounter instance relative to current thread
     * start() and stop() should come in pairs, or else maybe let memory leak
     * @param name performCounter name
     * @return a PerformCounter instance relative to current thread
     */
    public static PerformCounter start(String name){
        var perfCounter = THREAD_LOCAL.get();
        if(perfCounter == null) {
            perfCounter = new PerformCounter(name);
            THREAD_LOCAL.set(perfCounter);
        }
        return perfCounter;
    }

    /**
     * get a PerformCounter instance relative to current thread
     * if not exists, return a new instance but not relative to current thread,
     * as this method maybe use in anywhere, and not invole stop() method and can let memory leak.
     * @return PerformCounter instance
     */
    public static PerformCounter getPerformCounter() {
        var perfCounter = THREAD_LOCAL.get();
        if(perfCounter == null){
            perfCounter = new PerformCounter("default");
        }
        return perfCounter;
    }

    public String stop() {
        this.flush();
        var result = this.getResultMessage();
        THREAD_LOCAL.remove();
        return result;
    }
    // endregion

    private final String name;
    private String currentStageName = StringUtils.EMPTY;
    private long startTime = 0;
    private int currentStageIndex = 0;
    private int flushStageIndex = 0;
    private final List<StageInfo> stageInfoList = new ArrayList<>();
    private final List<PerformCounter> children = new ArrayList<>();
    private PerformCounter(String n) {
        this.name = n;
    }

    public void addStage(String stageName) {
        flush();
        currentStageName = stageName;
        startTime = System.currentTimeMillis();
        currentStageIndex++;
    }

    public void flush() {
        if(currentStageIndex > 0 && flushStageIndex < currentStageIndex) {
            stageInfoList.add(new StageInfo(currentStageName, System.currentTimeMillis() - startTime));
            flushStageIndex = currentStageIndex;
        }
        if(!children.isEmpty()) {
            children.forEach(PerformCounter::flush);
        }
    }

    public PerformCounter addSubPerformCounter(String name){
        flush();
        var subPerfCounter = new PerformCounter(name);
        children.add(subPerfCounter);
        stageInfoList.add(new StageInfo(name, subPerfCounter));
        return subPerfCounter;
    }

    public String getResultMessage() {
        StringBuilder sb = new StringBuilder(DEFAULT_INITIAL_MESSAGE_LENGHT);
        sb.append("{");
        for(int i = 0; i < stageInfoList.size(); i++) {
            var stage = stageInfoList.get(i);
            if(stage.subPerformCounter == null) {
                sb.append("\"").append(stage.stageName).append("\":").append("\"").append(stage.time).append("ms").append("\""); // "name":"256ms"
            } else {
                var subResultMessage = stage.subPerformCounter.getResultMessage();
                sb.append("\"").append(stage.subPerformCounter.name).append("\":").append(subResultMessage);
            }

            if(i < stageInfoList.size() -1){
                sb.append(",");
            }
        }
        sb.append("}");
        return sb.toString();
    }

    private static class StageInfo{
        String stageName;
        long time;
        PerformCounter subPerformCounter;
        public StageInfo(String name, long elapseTime) {
            this.stageName = name;
            this.time = elapseTime;
        }

        public StageInfo(String name, PerformCounter counter) {
            this.stageName = name;
            this.subPerformCounter = counter;
        }
    }

}
